﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using X3DXNA.services;
using X3DXNA.interfaces;
using X3DXNA.core.Boundings;
using X3DXNA.core.CustomNodes;

namespace X3DXNA.basicModels
{
    class basicCylinder : IGeometry
    {
        List<VertexPositionNormal> vertices;
        List<Int16> indices;

        VertexPositionNormal[] verticesArray;
        Int16[] indicesArray;

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;
        myBasicEffect basicEffect;
        MyBoundingBox boundingBox;

        Vector3 pos, size;
        Matrix world = Matrix.Identity, rotation;
        Game game;
        public String DEF { get; set; }
        public Matrix World { get { return world; } set { world = value; } }
        public Matrix getBasicTransformation { get { return Matrix.Identity; } }

        public void applyWorldTransformation(Matrix transformation)
        {
            world *= transformation;
        }

        public void applyRotation(Matrix value)
        {
            rotation = value;
            recalculateWorld();
        }

        private void recalculateWorld()
        {
            world = Matrix.Identity * rotation * Matrix.CreateTranslation(pos) * Matrix.CreateScale(size);
        }

        public basicCylinder(Game game, String DEF, float radius, float height, Matrix transformation)
        {
            this.DEF = DEF;
            this.game = game;
            this.world = transformation;

            vertices = new List<VertexPositionNormal>();
            indices = new List<Int16>();

            this.basicEffect = (myBasicEffect)game.Services.GetService(typeof(myBasicEffect));

            int tessellation = 35;

            height /= 2;

            // Create a ring of triangles around the outside of the cylinder.
            for (int i = 0; i < tessellation; i++)
            {
                Vector3 normal = GetCircleVector(i, tessellation);

                vertices.Add(new VertexPositionNormal(normal * radius + Vector3.Up * height, normal));
                vertices.Add(new VertexPositionNormal(normal * radius + Vector3.Down * height, normal));

                indices.Add((Int16)(i * 2));
                indices.Add((Int16)(i * 2 + 1));
                indices.Add((Int16)((i * 2 + 2) % (tessellation * 2)));

                indices.Add((Int16)(i * 2 + 1));
                indices.Add((Int16)((i * 2 + 3) % (tessellation * 2)));
                indices.Add((Int16)((i * 2 + 2) % (tessellation * 2)));
            }

            // Create flat triangle fan caps to seal the top and bottom.
            CreateCap(tessellation, height, radius, Vector3.Up);
            CreateCap(tessellation, height, radius, Vector3.Down);

            verticesArray = vertices.ToArray();
            indicesArray = indices.ToArray();

            //VertexPositionNormal.transform(verticesArray, transformation, verticesArray);

            Vector3[] points = new Vector3[verticesArray.Length];
            for (int i = 0; i < verticesArray.Length; i++)
            {
                points[i] = verticesArray[i].Position;
            }

            boundingBox = new MyBoundingBox(BoundingBox.CreateFromPoints(points));

            initializeVertexBuffer();
        }

        public void initializeVertexBuffer()
        {
            vertexBuffer = new VertexBuffer(
                game.GraphicsDevice, 
                typeof(VertexPositionNormal), 
                verticesArray.Length, 
                BufferUsage.WriteOnly);

            indexBuffer = new IndexBuffer(game.GraphicsDevice, typeof(Int16), indicesArray.Length, BufferUsage.WriteOnly);

            vertexBuffer.SetData<VertexPositionNormal>(verticesArray, 0, verticesArray.Length);
            indexBuffer.SetData<Int16>(indicesArray);
        }

        public void draw()
        {
            game.GraphicsDevice.SetVertexBuffer(vertexBuffer);
            game.GraphicsDevice.Indices = indexBuffer;

            foreach (EffectPass pass in basicEffect.getBasicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                game.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    0,
                    0,
                    verticesArray.Length,
                    0,
                    indicesArray.Length / 3
                );
            }
        }

        /// <summary>
        /// Helper method computes a point on a circle.
        /// </summary>
        static Vector3 GetCircleVector(int i, int tessellation)
        {
            float angle = i * MathHelper.TwoPi / tessellation;

            float dx = (float)Math.Cos(angle);
            float dz = (float)Math.Sin(angle);

            return new Vector3(dx, 0, dz);
        }

        /// <summary>
        /// Helper method creates a triangle fan to close the ends of the cylinder.
        /// </summary>
        void CreateCap(int tessellation, float height, float radius, Vector3 normal)
        {
            // Create cap indices.
            for (int i = 0; i < tessellation - 2; i++)
            {
                if (normal.Y > 0)
                {
                    indices.Add((Int16)(vertices.Count));
                    indices.Add((Int16)(vertices.Count + (i + 1) % tessellation));
                    indices.Add((Int16)(vertices.Count + (i + 2) % tessellation));
                }
                else
                {
                    indices.Add((Int16)(vertices.Count));
                    indices.Add((Int16)(vertices.Count + (i + 2) % tessellation));
                    indices.Add((Int16)(vertices.Count + (i + 1) % tessellation));
                }
            }

            // Create cap vertices.
            for (int i = 0; i < tessellation; i++)
            {
                Vector3 position = GetCircleVector(i, tessellation) * radius +
                                   normal * height;

                vertices.Add(new VertexPositionNormal(position, normal));
            }
        }

        public IBounding retrieveBounding()
        {
            return boundingBox;
        }

        public Vector3[] getTriangleVertices()
        {
            return null;
        }
    }
}
